<!--
  - Code Style:
    * Do not use any JavaScript or CSS frameworks or preprocessors.
    * This HTML page should not require any build systems (node.js, npm, gulp, etc.)
    * This HTML page should not be minified, instead it should be reasonably minimalistic by itself.
    * This HTML page should not load any external resources on load. (CSS and JavaScript must be embedded directly to the page. No external fonts or images should be loaded).
    * This UI should look as lightweight, clean and fast as possible.
    * All UI elements must be aligned in pixel-perfect way.
    * There should not be any animations.
    * No unexpected changes in positions of elements while the page is loading.
    * Navigation by keyboard should work. 64-bit numbers must display correctly.

  - Development Roadmap:
    * Support readonly servers.
      - Check if readonly = 1 (with SELECT FROM system.settings) to avoid sending settings.
      - It can be done once on address/credentials change.
      - It can be done in background, e.g. wait 100 ms after address/credentials change and do the check.
      - Also, it can provide visual indication that credentials are correct.
-->

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <link rel="icon"
              href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🐤</text></svg>"
        >

        <title>quackpipe</title>

        <style> :root {
            --background-color: #DDF8FF; /* Or #FFFBEF; actually many pastel colors look great for light theme. */
            --element-background-color: #FFF;
            --bar-color: #F8F4F0; /* Light bar in background of table cells. */
            --border-color: #EEE;
            --shadow-color: rgba(0, 0, 0, 0.1);
            --button-color: #FFAA00; /* Orange on light-cyan is especially good. */
            --text-color: #000;
            --button-active-color: #F00;
            --button-active-text-color: #FFF;
            --misc-text-color: #888;
            --error-color: #FEE; /* Light-pink on light-cyan is so neat, I even want to trigger errors to see this cool combination of colors. */
            --table-header-color: #F8F8F8;
            --table-hover-color: #FFF8EF;
            --null-color: #A88;
            --link-color: #06D;
            --logo-color: #CEE;
            --logo-color-active: #BDD;
        }

        [data-theme="dark"] {
            --background-color: #000;
            --element-background-color: #102030;
            --bar-color: #182838;
            --border-color: #111;
            --shadow-color: rgba(255, 255, 255, 0.1);
            --text-color: #CCC;
            --button-color: #FFAA00;
            --button-text-color: #000;
            --button-active-color: #F00;
            --button-active-text-color: #FFF;
            --misc-text-color: #888;
            --error-color: #400;
            --table-header-color: #102020;
            --table-hover-color: #003333;
            --null-color: #A88;
            --link-color: #4BDAF7;
            --logo-color: #222;
            --logo-color-active: #333;
        }

        * {
            box-sizing: border-box; /* For iPad */
            margin: 0;
            border-radius: 0;
            tab-size: 4;
        }

        html, body {
            height: 100%;
            margin: 0; /* This enables position: sticky on controls */
            overflow: auto;
        }

        html { /* The fonts that have full support for hinting. */
            font-family: Liberation Sans, DejaVu Sans, sans-serif, Noto Color Emoji, Apple Color Emoji, Segoe UI Emoji;
            background: var(--background-color);
            color: var(--text-color);
        }

        body { /* This element will show scroll-bar on overflow, and the scroll-bar will be outside of the padding. */
            padding: 0.5rem;
        }

        #controls { /* When a page will be scrolled horizontally due to large table size, keep controls in place. */
            position: sticky;
            left: 0;
        }

        /* Otherwise Webkit based browsers will display ugly border on focus. */
        textarea, input, button {
            outline: none;
            border: none;
            color: var(--text-color);
        }

        .monospace { /* Prefer fonts that have full hinting info. This is important for non-retina displays. Also I personally dislike "Ubuntu" font due to the similarity of 'r' and 'г' (it looks very ignorant). */
            font-family: Liberation Mono, DejaVu Sans Mono, MonoLisa, Consolas, monospace;
        }

        .monospace-table { /* Liberation is worse than DejaVu for block drawing characters. */
            font-family: DejaVu Sans Mono, Liberation Mono, MonoLisa, Consolas, monospace;
        }

        .shadow {
            box-shadow: 0 0 1rem var(--shadow-color);
        }

        input, textarea {
            border: 1px solid var(--border-color); /* The font must be not too small (to be inclusive) and not too large (it's less practical and make general feel of insecurity) */
            font-size: 11pt;
            padding: 0.25rem;
            background-color: var(--element-background-color);
        }

        #query { /* Make enough space for even big queries. */
            height: 20vh; /* Keeps query text-area's width full screen even when user adjusting the width of the query box. */
            min-width: 100%;
        }

        #inputs {
            white-space: nowrap;
        }

        #url {
            width: 70%;
        }

        #user {
            width: 15%;
        }

        #password {
            width: 15%;
        }

        #run_div {
            margin-top: 1rem;
        }

        #run {
            color: var(--button-text-color);
            background-color: var(--button-color);
            padding: 0.25rem 1rem;
            cursor: pointer;
            font-weight: bold;
            font-size: 100%; /* Otherwise button element will have lower font size. */
        }

        #run:hover, #run:focus {
            color: var(--button-active-text-color);
            background-color: var(--button-active-color);
        }

        #stats {
            float: right;
            color: var(--misc-text-color);
        }

        #toggle-light, #toggle-dark {
            float: right;
            padding-right: 0.5rem;
            cursor: pointer;
        }

        .hint {
            color: var(--misc-text-color);
        }

        #data_div {
            margin-top: 1rem;
        }

        #data-table {
            width: 100%;
            border-collapse: collapse;
            border-spacing: 0;
        }

        /* Will be displayed when user specified custom format. */
        #data-unparsed {
            background-color: var(--element-background-color);
            margin-top: 0rem;
            padding: 0.25rem 0.5rem;
            display: none;
        }

        td {
            background-color: var(--element-background-color); /* For wide tables any individual column will be no more than 50% of page width. */
            max-width: 50vw; /* The content is cut unless you hover. */
            overflow: hidden;
            text-overflow: ellipsis;
            padding: 0.25rem 0.5rem;
            border: 1px solid var(--border-color);
            white-space: pre;
            vertical-align: top;
        }

        .right {
            text-align: right;
        }

        th {
            padding: 0.25rem 0.5rem;
            text-align: center;
            background-color: var(--table-header-color);
            border: 1px solid var(--border-color);
        }

        /* The row under mouse pointer is highlight for better legibility. */
        tr:hover, tr:hover td {
            background-color: var(--table-hover-color);
        }

        tr:hover {
            box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
        }

        #error {
            background: var(--error-color);
            white-space: pre-wrap;
            padding: 0.5rem 1rem;
            display: none;
        }

        /* When mouse pointer is over table cell, will display full text (with wrap) instead of cut. * We also keep it for some time on mouseout for "hysteresis" effect. */
        td.left:hover, .td-hover-hysteresis {
            white-space: pre-wrap;
            max-width: none;
        }

        .td-selected {
            white-space: pre-wrap;
            max-width: none;
            background-color: var(--table-hover-color);
            border: 2px solid var(--border-color);
        }

        td.transposed {
            max-width: none;
            overflow: auto;
            white-space: pre-wrap;
        }

        td.empty-result {
            text-align: center;
            vertical-align: middle;
        }

        .row-number {
            width: 1%;
            text-align: right;
            background-color: var(--table-header-color);
            color: var(--misc-text-color);
        }

        div.empty-result {
            opacity: 10%;
            font-size: 7vw;
            font-family: Liberation Sans, DejaVu Sans, sans-serif;
        }

        /* The style for SQL NULL */
        .null {
            color: var(--null-color);
        }

        @keyframes hourglass-animation {
            0% {
                transform: rotate(-180deg);
            }
            50% {
                transform: rotate(-180deg);
            }
            100% {
                transform: none;
            }
        }

        #hourglass {
            display: none;
            margin-left: 1rem;
            font-size: 110%;
            color: #888;
            animation: hourglass-animation 1s linear infinite;
        }

        #check-mark {
            display: none;
            padding-left: 1rem;
            font-size: 110%;
            color: #080;
        }

        a, a:visited {
            color: var(--link-color);
            text-decoration: none;
        }

        #graph {
            display: none;
        }

        /* This is for graph in svg */
        text {
            font-size: 14px;
            fill: var(--text-color);
        }

        .node rect {
            fill: var(--element-background-color);
            filter: drop-shadow(.2rem .2rem .2rem var(--shadow-color));
        }

        .edgePath path {
            stroke: var(--text-color);
        }

        marker {
            fill: var(--text-color);
        }

        #logo {
            fill: var(--logo-color);
        }

        #logo:hover {
            fill: var(--logo-color-active);
        }

        #logo-container {
            text-align: center;
            margin-top: 5em;
        }

        #chart {
            background-color: var(--element-background-color);
            filter: drop-shadow(.2rem .2rem .2rem var(--shadow-color));
            display: none;
            height: 70vh;
        }

        /* This is for charts (uPlot), Copyright (c) 2022 Leon Sorokin, MIT License, https://github.com/leeoniya/uPlot/ */
        .u-wrap {
            position: relative;
            user-select: none;
        }

        .u-over, .u-under, .u-axis {
            position: absolute;
        }

        .u-under {
            overflow: hidden;
        }

        .uplot canvas {
            display: block;
            position: relative;
            width: 100%;
            height: 100%;
        }

        .u-legend {
            margin: auto;
            text-align: center;
            margin-top: 1em;
            font-family: Liberation Mono, DejaVu Sans Mono, MonoLisa, Consolas, monospace;
        }

        .u-inline {
            display: block;
        }

        .u-inline * {
            display: inline-block;
        }

        .u-inline tr {
            margin-right: 16px;
        }

        .u-legend th {
            font-weight: 600;
        }

        .u-legend th > * {
            vertical-align: middle;
            display: inline-block;
        }

        .u-legend td {
            min-width: 13em;
        }

        .u-legend .u-marker {
            width: 1em;
            height: 1em;
            margin-right: 4px;
            background-clip: padding-box !important;
        }

        .u-inline.u-live th::after {
            content: ":";
            vertical-align: middle;
        }

        .u-inline:not(.u-live) .u-value {
            display: none;
        }

        .u-series > * {
            padding: 4px;
        }

        .u-series th {
            cursor: pointer;
        }

        .u-legend .u-off > * {
            opacity: 0.3;
        }

        .u-select {
            background: rgba(0, 0, 0, 0.07);
            position: absolute;
            pointer-events: none;
        }

        .u-cursor-x, .u-cursor-y {
            position: absolute;
            left: 0;
            top: 0;
            pointer-events: none;
            will-change: transform;
            z-index: 100;
        }

        .u-hz .u-cursor-x, .u-vt .u-cursor-y {
            height: 100%;
            border-right: 1px dashed #607D8B;
        }

        .u-hz .u-cursor-y, .u-vt .u-cursor-x {
            width: 100%;
            border-bottom: 1px dashed #607D8B;
        }

        .u-cursor-pt {
            position: absolute;
            top: 0;
            left: 0;
            border-radius: 50%;
            border: 0 solid;
            pointer-events: none;
            will-change: transform;
            z-index: 100; /*this has to be !important since we set inline "background" shorthand */
            background-clip: padding-box !important;
        }

        .u-axis.u-off, .u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {
            display: none;
        } </style>
    </head>

    <body>
        <div id="controls">
            <div id="inputs">
                <input class="monospace shadow" id="url" type="text" value="http://localhost:8123/" placeholder="url"/>
                <input class="monospace shadow" id="user" type="text" value="default"placeholder="user"/>
                <input class="monospace shadow" id="password" type="password" placeholder="password"/>
            </div>
            <div id="query_div">
                <textarea autofocus spellcheck="false" class="monospace shadow" id="query"></textarea>
            </div>

            <div id="run_div">
                <button class="shadow" id="run">Run</button>
                <span class="hint">&nbsp;(Ctrl/Cmd+Enter)</span> <span id="hourglass">⧗</span> <span id="check-mark">✔</span>
                <select class="shadow" id="dropdown" style="margin-left: 5px;"> <!-- newline: &#13;&#10; single quote: &#39; -->
                    <option value=""></option>
                    <option value='SELECT version();'>Version</option>
                    <option value='SELECT * &#13;&#10;FROM read_csv_auto("https://s3.us-east-1.amazonaws.com/altinity-clickhouse-data/airline/data/airports/Airports.csv")&#13;&#10;WHERE Country == &#39;Italy&#39;&#13;&#10;ORDER BY City ASC'>
                        Scan S3/CSV
                    </option>
                    <option value='SELECT town,district,count() AS c,&#13;&#10;FROM read_parquet("https://datasets-documentation.s3.eu-west-3.amazonaws.com/house_parquet/house_0.parquet")&#13;&#10;WHERE read_parquet.town == &#39;LONDON&#39;&#13;&#10;GROUP BY town,district&#13;&#10;ORDER BY c DESC&#13;&#10;LIMIT 10'>
                        Scan/Group S3/Parquet
                    </option>
                    <option value='SELECT count(*) FROM "https://shell.duckdb.org/data/tpch/0_01/parquet/lineitem.parquet";'>
                        Count HTTP/Parquet
                    </option>
                    <option value='SELECT avg(c_acctbal) FROM "https://shell.duckdb.org/data/tpch/0_01/parquet/customer.parquet";'>
                        Avg HTTP/Parquet
                    </option>
                    <option value='SELECT n_name, count(*)&#13;&#10; FROM "https://shell.duckdb.org/data/tpch/0_01/parquet/customer.parquet",&#13;&#10; "https://shell.duckdb.org/data/tpch/0_01/parquet/nation.parquet"&#13;&#10; WHERE c_nationkey = n_nationkey GROUP BY n_name;'>
                        Join HTTP/Parquet
                    </option>
                </select>
                <span id="stats"></span>
                <span id="toggle-dark">🌑</span>
                <span id="toggle-light">🌞</span>
            </div>
        </div>

        <div id="data_div">
            <table class="monospace-table shadow" id="data-table"></table>
            <pre class="monospace-table shadow" id="data-unparsed"></pre>
        </div>

        <div id="chart"></div>
        <svg id="graph" fill="none"></svg>
        <p id="error" class="monospace shadow"></p>
        <p id="logo-container"></p>
    </body>

    <script type="text/javascript"> let request_num = 0;
        var querybox = document.getElementById("query"), querypresets = document.getElementById("dropdown");
        querypresets.onchange = function () {
            var e = querypresets.options[querypresets.selectedIndex].value;
            querybox.value = e
        };
        let previous_query = "";
        const current_url = new URL(window.location), server_address = current_url.searchParams.get("url");
        server_address ? document.getElementById("url").value = server_address : "file:" != location.protocol && (document.getElementById("url").value = location.origin);
        const user_from_url = current_url.searchParams.get("user");

        function postImpl(e, t) {
            let a = document.getElementById("user").value, r = document.getElementById("password").value,
                l = document.getElementById("url").value;
            var n = l + (l.indexOf("?") >= 0 ? "&" : "?") + "add_http_cors_header=1&default_format=JSONCompact&max_result_rows=1000&max_result_bytes=10000000&result_overflow_mode=break";
            document.location.href.startsWith("file://") && (n += "&user=" + encodeURIComponent(a) + "&password=" + encodeURIComponent(r));
            let s = new XMLHttpRequest;
            s.open("POST", n, !0), document.location.href.startsWith("file://") || s.setRequestHeader("Authorization", "Basic " + btoa(a + ":" + r)), s.onreadystatechange = function () {
                if (e == request_num && this.readyState === XMLHttpRequest.DONE && (renderResponse(this.status, this.response), t != previous_query)) {
                    let r = {query: t, status: this.status, response: this.response.length > 1e5 ? null : this.response},
                        n = "Query: " + t, s = window.location.pathname + "?user=" + encodeURIComponent(a);
                    l != location.origin && (s += "&url=" + encodeURIComponent(l)), s += "#" + window.btoa(t), "" == previous_query ? history.replaceState(r, n, s) : history.pushState(r, n, s), document.title = n, previous_query = t
                }
            }, document.getElementById("check-mark").style.display = "none", document.getElementById("hourglass").style.display = "inline-block", s.send(t)
        }

        function renderResponse(e, t) {
            if (document.getElementById("hourglass").style.display = "none", 200 === e) {
                let a;
                try {
                    a = JSON.parse(t)
                } catch (r) {
                }
                void 0 !== a && void 0 !== a.statistics ? renderResult(a) : Array.isArray(a) && 2 == a.length && Array.isArray(a[0]) && Array.isArray(a[1]) && a[0].length > 1 && a[0].length == a[1].length ? renderChart(a) : renderUnparsedResult(t), document.getElementById("check-mark").style.display = "inline"
            } else renderError(t)
        }

        user_from_url && (document.getElementById("user").value = user_from_url);
        let query_area = document.getElementById("query");

        function post() {
            ++request_num;
            postImpl(request_num, query_area.value)
        }

        window.onpopstate = function (e) {
            if (e.state) {
                if (query_area.value = e.state.query, !e.state.response) {
                    clear();
                    return
                }
                renderResponse(e.state.status, e.state.response)
            }
        }, window.location.hash && (query_area.value = window.atob(window.location.hash.substr(1))), document.getElementById("run").onclick = function () {
            post()
        }, document.onkeydown = function (e) {
            (e.metaKey || e.ctrlKey) && (13 == e.keyCode || 10 == e.keyCode) && post()
        };
        let user_prefers_tab_navigation = !1;

        function clearElement(e) {
            let t = document.getElementById(e);
            for (; t.firstChild;) t.removeChild(t.lastChild);
            t.style.display = "none"
        }

        function clear() {
            clearElement("data-table"), clearElement("graph"), clearElement("chart"), clearElement("data-unparsed"), clearElement("error"), document.getElementById("check-mark").display = "none", document.getElementById("hourglass").display = "none", document.getElementById("stats").innerText = "", document.getElementById("logo-container").style.display = "block"
        }

        function formatReadable(e = 0, t = 2, a = []) {
            let r = e ? Math.floor(Math.log(e) / Math.log(1e3)) : 0, l = a[r];
            return Number(e / Math.pow(1e3, r)).toFixed(l ? t : 0) + l
        }

        function formatReadableBytes(e) {
            return formatReadable(e, 2, [" B", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB"])
        }

        function formatReadableRows(e) {
            return formatReadable(e, 2, ["", " thousand", " million", " billion", " trillion", " quadrillion"])
        }

        function renderResult(e) {
            clear();
            let t = document.getElementById("stats"), a = e.statistics.elapsed.toFixed(3), r = e.statistics.rows_read,
                l = e.statistics.bytes_read, n = formatReadableBytes(l), s = formatReadableRows(r);
            t.innerText = `Elapsed: ${a} sec, read ${s} rows, ${n}.`, e.data.length > 3 && query_area.value.match(/^\s*EXPLAIN/i) && "string" == typeof e.data[0][0] && e.data[0][0].startsWith("digraph") ? renderGraph(e) : renderTable(e)
        }

        function renderCell(e, t, a) {
            let r = document.createElement("td"), l = null === e, n = !1, s;
            l ? s = "ᴺᵁᴸᴸ" : "object" == typeof e ? s = JSON.stringify(e) : (s = e, "string" == typeof e && e.match(/^https?:\/\/\S+$/) && (n = !0));
            let d = document.createTextNode(s);
            if (n) {
                let o = document.createElement("a");
                o.appendChild(d), o.href = s, o.setAttribute("target", "_blank"), d = o
            }
            if (a.is_transposed ? r.className = "left transposed" : r.className = a.column_is_number[t] ? "right" : "left", l && (r.className += " null"), !a.is_transposed && a.column_need_render_bars[t] && s > 0) {
                let i = 100 * s / a.column_maximums[t], c = document.createElement("div");
                c.style.width = "100%", c.style.background = `linear-gradient(to right, var(--bar-color) 0%, var(--bar-color) ${i}%, transparent ${i}%, transparent 100%)`, c.appendChild(d), d = c
            }
            return r.appendChild(d), r
        }

        function renderTableTransposed(e) {
            let t = document.createElement("tbody");
            for (let a in e.meta) {
                let r = document.createElement("tr");
                {
                    let l = document.createElement("th");
                    l.className = "right", l.style.width = "0", l.appendChild(document.createTextNode(e.meta[a].name)), r.appendChild(l)
                }
                for (let n in e.data) {
                    let s = e.data[n][a], d = renderCell(s, a, {is_transposed: !0});
                    r.appendChild(d)
                }
                if (0 == e.data.length && 0 == a) {
                    let o = document.createElement("td");
                    o.rowSpan = e.meta.length, o.className = "empty-result";
                    let i = document.createElement("div");
                    i.appendChild(document.createTextNode("empty result")), i.className = "empty-result", o.appendChild(i), r.appendChild(o)
                }
                t.appendChild(r)
            }
            let c = document.getElementById("data-table");
            c.appendChild(t), c.style.display = "table"
        }

        function renderTable(e) {
            if (e.data.length <= 1 && e.meta.length >= 5) {
                renderTableTransposed(e);
                return
            }
            let t = e.data.length > 3, a = document.createElement("thead");
            if (t) {
                let r = document.createElement("th");
                r.className = "row-number", r.appendChild(document.createTextNode("№")), a.appendChild(r)
            }
            for (let l in e.meta) {
                let n = document.createElement("th"), s = document.createTextNode(e.meta[l].name);
                n.appendChild(s), a.appendChild(n)
            }
            let d = 1e4 / e.meta.length, o = 0, i = e.meta.map(e => !!e.type.match(/^(Nullable\()?(U?Int|Decimal|Float)/)),
                c = i.map((t, a) => t ? Math.max(...e.data.map(e => e[a])) : 0),
                u = i.map((t, a) => t ? Math.min(...e.data.map(e => Math.max(0, e[a]))) : 0),
                m = i.map((e, t) => c[t] > 0 && c[t] > u[t]), p = {
                    is_transposed: !1,
                    column_is_number: i,
                    column_maximums: c,
                    column_minimums: u,
                    column_need_render_bars: m
                }, h = performance.now(), y = document.createElement("tbody");
            for (let g in e.data) {
                let $ = document.createElement("tr");
                if (t) {
                    let f = document.createElement("td");
                    f.className = "row-number", f.appendChild(document.createTextNode(1 + +g)), $.appendChild(f)
                }
                for (let E in e.data[g]) {
                    let b = e.data[g][E], v = renderCell(b, E, p);
                    v.onclick = () => {
                        v.classList.add("td-selected")
                    }, v.onmouseenter = () => {
                        v.classList.add("td-hover-hysteresis"), v.onmouseleave = () => {
                            setTimeout(() => {
                                v && v.classList.remove("td-hover-hysteresis")
                            }, 1e3)
                        }
                    }, $.appendChild(v)
                }
                if (y.appendChild($), ++o >= d && performance.now() - h >= 200) break
            }
            let k = document.getElementById("data-table");
            k.appendChild(a), k.appendChild(y), k.style.display = "table"
        }

        function renderUnparsedResult(e) {
            clear();
            let t = document.getElementById("data-unparsed");
            "" === e && (e = "Ok."), t.innerText = e, t.style.display = "inline-block"
        }

        function renderError(e) {
            clear(), document.getElementById("error").innerText = e || "No response.", document.getElementById("error").style.display = "block", document.getElementById("logo-container").style.display = "none"
        }

        function loadJS(e, t) {
            return new Promise((a, r) => {
                let l = document.createElement("script");
                l.src = e, t ? (l.crossOrigin = "anonymous", l.integrity = t) : console.warn("no integrity for", e), l.addEventListener("load", function () {
                    a(!0)
                }), document.head.appendChild(l)
            })
        }

        [...document.querySelectorAll("input")].map(e => {
            e.onkeydown = e => {
                "Tab" == e.key && (user_prefers_tab_navigation = !0)
            }
        }), query_area.onkeydown = e => {
            if ("Tab" == e.key && !event.shiftKey && !user_prefers_tab_navigation) {
                let t = e.target, a = t.selectionStart;
                return t.selectionEnd, t.value = t.value.substring(0, t.selectionStart) + " " + t.value.substring(t.selectionEnd), t.selectionStart = a + 4, t.selectionEnd = a + 4, e.preventDefault(), !1
            }
        };
        let load_dagre_promise;

        function loadDagre() {
            return load_dagre_promise || (load_dagre_promise = Promise.all([loadJS("https://dagrejs.github.io/project/dagre/v0.8.5/dagre.min.js", "sha384-2IH3T69EIKYC4c+RXZifZRvaH5SRUdacJW7j6HtE5rQbvLhKKdawxq6vpIzJ7j9M"), loadJS("https://dagrejs.github.io/project/graphlib-dot/v0.6.4/graphlib-dot.min.js", "sha384-Q7oatU+b+y0oTkSoiRH9wTLH6sROySROCILZso/AbMMm9uKeq++r8ujD4l4f+CWj"), loadJS("https://dagrejs.github.io/project/dagre-d3/v0.6.4/dagre-d3.min.js", "sha384-9N1ty7Yz7VKL3aJbOk+8ParYNW8G5W+MvxEfFL9G7CRYPmkHI9gJqyAfSI/8190W"), loadJS("https://cdn.jsdelivr.net/npm/d3@7.0.0", "sha384-S+Kf0r6YzKIhKA8d1k2/xtYv+j0xYUU3E7+5YLrcPVab6hBh/r1J6cq90OXhw80u"),]))
        }

        async function renderGraph(e) {
            await loadDagre();
            let t = e.data.reduce((e, t) => e + "\n" + t[0].replace(/shape\s*=\s*box/g, "shape=rect")), a = graphlibDot.read(t);
            a.graph().rankdir = "TB";
            let r = new dagreD3.render, l = document.getElementById("graph");
            l.style.display = "block", r(d3.select("#graph"), a), l.style.width = a.graph().width, l.style.height = a.graph().height
        }

        let load_uplot_promise;

        function loadUplot() {
            return load_uplot_promise || (load_uplot_promise = loadJS("https://cdn.jsdelivr.net/npm/uplot@1.6.21/dist/uPlot.iife.min.js", "sha384-TwdJPnTsKP6pnvFZZKda0WJCXpjcHCa7MYHmjrYDu6rsEsb/UnFdoL0phS5ODqTA"))
        }

        let uplot;

        async function renderChart(e) {
            await loadUplot(), clear();
            let t = document.getElementById("chart");
            t.style.display = "block";
            let a = uPlot.paths.stepped({align: 1}), [r, l, n, s] = "light" == theme ? ["#F80", "#FED", "#c7d0d9", "#2c3235"] : ["#888", "#045", "#2c3235", "#c7d0d9"],
                d = {
                    width: t.clientWidth,
                    height: t.clientHeight,
                    scales: {x: {time: e[0][0] > 1e9 && e[0][0] < 2e9}},
                    axes: [{
                        stroke: s,
                        grid: {width: 1 / devicePixelRatio, stroke: n},
                        ticks: {width: 1 / devicePixelRatio, stroke: n}
                    }, {
                        stroke: s,
                        grid: {width: 1 / devicePixelRatio, stroke: n},
                        ticks: {width: 1 / devicePixelRatio, stroke: n}
                    }],
                    series: [{label: "x"}, {label: "y", stroke: r, fill: l, drawStyle: 0, lineInterpolation: 1, paths: a}],
                    padding: [null, null, null, (Math.ceil(Math.log10(Math.max(...e[1]))) + Math.floor(Math.log10(Math.max(...e[1])) / 3)) * 6]
                };
            uplot = new uPlot(d, e, t)
        }

        function resizeChart() {
            if (uplot) {
                let e = document.getElementById("chart");
                uplot.setSize({width: e.clientWidth, height: e.clientHeight})
            }
        }

        function redrawChart() {
            uplot && "block" == document.getElementById("chart").style.display && renderChart(uplot.data)
        }

        new ResizeObserver(resizeChart).observe(document.getElementById("chart"));
        let theme = current_url.searchParams.get("theme");

        function setColorTheme(e, t) {
            theme = e, t && window.localStorage.setItem("theme", theme), document.documentElement.setAttribute("data-theme", theme), redrawChart()
        }

        if (-1 === ["dark", "light"].indexOf(theme) && (theme = window.localStorage.getItem("theme")), theme || (theme = "dark"), theme) document.documentElement.setAttribute("data-theme", theme); else {
            let e = window.matchMedia("(prefers-color-scheme: dark)");
            e.matches && setColorTheme("dark"), e.addEventListener("change", function (e) {
                setColorTheme(e.matches ? "dark" : "light")
            })
        }
        document.getElementById("toggle-light").onclick = function () {
            setColorTheme("light", !0)
        }, document.getElementById("toggle-dark").onclick = function () {
            setColorTheme("dark", !0)
        };
    </script>
</html>